-- Matías Blanco & Sergio Pérez
-- Dragon Ball - Tipos Protegidos en ADA

with Ada.Text_Io ; use Ada.Text_Io;
with Ada.Integer_Text_Io; use Ada.Integer_Text_Io;
with Ada.Numerics.Float_Random ; use Ada.Numerics.Float_Random;

procedure DragonBallTProtAda is
   -- Costantes y variables
   MAXCIUDADES : constant integer := 10;
   MAXBOLAS : constant integer := 7;
   MAXESBIRROS : constant integer := 3;
   MAXGOKUCIA : constant integer := 3;

   G : Generator;

   -- Definimos los arrays
   type bolasGokuCIA is array(1..MAXGOKUCIA) of integer;
   type bolasEsbirros is array(1..MAXGOKUCIA) of integer;

   -- Definimos los tipos protegidos
   protected type Ciudad is
      procedure Iniciar(id : integer; hayBola : boolean);
      procedure EntrarGokuCIA(id: integer; bolas : integer; algunAmigo : out boolean);
      procedure EntrarEsbirros(id: integer; bolas : integer; algunAmigo : out boolean);
      entry SalirGokuCIA(bolas : out integer);
      entry SalirEsbirros(bolas : out integer);
      private
         idCiudad : integer;
         Bola : boolean; -- Indica si hay bola o no en esta ciudad
         hayGokuCIA : boolean; -- Indica si esta Goku o alguno de los suyos en la Ciudad
         hayEsbirros : boolean; -- Indica si hay algun esbirro en la Ciudad
         bolasGokuCIA : integer; -- Bolas con las que entra goku o alguno de los amigos a la ciudad
         bolasEsbirros : integer; -- Bolas con las que entra algun esbirro a la ciudad
         luchando : boolean; -- Indica si se esta luchando en ese momento en la isla
   end Ciudad;
   
   protected type BancoBolasGokuCia is
      procedure MeterBolas(id : integer; nbolas : integer);
      procedure DarBolas(id : integer; nbolas : out integer);
      procedure DarTodasBolas(nbolas : out integer);
      private
         Bolas : bolasGokuCIA := (others => 0); -- Guarda el numero de bolas que tiene cada uno
   end BancoBolasGokuCia;

   protected type BancoBolasEsbirros is
      procedure MeterBolas(id : integer; nbolas : integer);
      procedure DarBolas(id : integer; nbolas : out integer);
      procedure DarTodasBolas(nbolas : out integer);
      private
         Bolas : bolasEsbirros := (others => 0); -- Guarda el numero de bolas que tiene cada uno
   end BancoBolasEsbirros;
   
   -- Definimos las tareas
   task type GokuCIA(idGokuCIA : integer); -- Representa a Goku y a sus amigos.
   task type Esbirro(idEsbirro : integer); -- Representa a Goku y a sus amigos.
   

   -- Definimos los tipos protegidos
   bancoGokuCIA : BancoBolasGokuCia;
   bancoEsbirros : BancoBolasEsbirros;
   ciudades: array(1..MAXCIUDADES) of Ciudad;

   type PGokuCIA is access GokuCIA; -- Definitos el tipo puntero de Goku y sus amigos
   -- Definimos el array de punteros de GokuCIA
   amigosGoku: array(1..MAXGOKUCIA) of PGokuCIA;

   type PEsbirro is access Esbirro; -- Definitos el tipo puntero de Esbirros
   -- Definimos el array de punteros de Esbirro
   Esbirros: array(1..MAXESBIRROS) of PEsbirro;

   protected body Ciudad is
      procedure Iniciar(id : integer; hayBola : boolean) is
      begin
         idCiudad := id;
         Bola := hayBola;
         hayGokuCIA := false; -- Indica si esta Goku o alguno de los suyos en la Ciudad
         hayEsbirros := false; -- Indica si hay algun esbirro en la Ciudad
         bolasGokuCIA := 0; -- Bolas con las que entra goku o alguno de los amigos a la ciudad
         bolasEsbirros := 0; -- Bolas con las que entra algun esbirro a la ciudad
         luchando := false;
      end Iniciar;      

      procedure EntrarGokuCIA(id: integer; bolas : integer; algunAmigo : out boolean) is
         rand : float;
      begin
         if hayGokuCIA then -- Quiere decirque ya hay alguno de su clan en la Ciudad
            algunAmigo := true;
         else -- No hay ninguno
            algunAmigo := false; 
            hayGokuCIA := true; -- Indicamos que hemos entrado

            bolasGokuCIA := bolas;

            -- Vemos si hay algun enemigo para luchar con el
            if hayEsbirros then
               luchando := true; -- Indicamos que se esta luchando
               -- Generamos quien gana
               Reset(G);
               rand := Random(G);
               if rand < 0.8 then -- Gana GokuCIA. Como son los bienos tienen las posibilidades
                  bolasGokuCIA := bolasGokuCIA + bolasEsbirros; -- Le quitamos las bolas
                  bolasEsbirros := 0;
                  Put("¡¡¡ Ha habido una lucha y ha ganado Goku o CIA con id:");Put(id,0);Put(" !!!");New_Line;
               else -- Gana los Esbirros
                  bolasEsbirros := bolasEsbirros + bolasGokuCIA; -- Le quitamos las bolas
                  bolasGokuCIA := 0;
                  Put("¡¡¡ Ha perdido una lucha y ha ganado Goku o CIA con id:");Put(id,0);Put(" !!!");New_Line;
               end if;
               luchando := false;
            end if;
         end if;
      end EntrarGokuCIA;
      
      procedure EntrarEsbirros(id: integer; bolas : integer; algunAmigo : out boolean) is
         rand : float;
      begin
         if hayEsbirros then -- Quiere decirque ya hay alguno de su clan en la Ciudad
            algunAmigo := true;
         else -- No hay ninguno
            algunAmigo := false; 
            hayEsbirros := true; -- Indicamos que hemos entrado

            bolasEsbirros := bolas;

            -- Vemos si hay algun enemigo para luchar con el
            if hayGokuCIA then
               luchando := true; -- Indicamos que se esta luchando
               -- Generamos quien gana
               Reset(G);
               rand := Random(G);
               if rand >= 0.8 then -- Gana los Esbirros
                  bolasEsbirros := bolasEsbirros + bolasGokuCIA; -- Le quitamos las bolas
                  bolasGokuCIA := 0;
                  Put("¡¡¡ Ha habido una lucha y ha ganado Esbirro con id:");Put(id,0);Put(" !!!");New_Line;
               else -- Gana los Esbirros
                  bolasGokuCIA := bolasGokuCIA + bolasEsbirros; -- Le quitamos las bolas
                  bolasEsbirros := 0;
                  Put("¡¡¡ Ha habido una lucha y ha perdido Esbirro con id:");Put(id,0);Put(" !!!");New_Line;
               end if;
               luchando := false;
            end if;
         end if;
      end EntrarEsbirros;

      entry SalirGokuCIA(bolas : out integer) when not luchando is -- Cuando se esta luchando no se puede salir
      begin
         if Bola then -- Si hay bola la cojemos
            bolas := 1;
            bola := false;
         else
            bolas := 0;
         end if;

         bolas := bolas + bolasGokuCIA; -- Devolvemos las bolas con las que sale de la Ciudad
         hayGokuCIA := false; -- Indica que ya se ha ido de la ciudad
      end SalirGokuCIA;

      entry SalirEsbirros(bolas : out integer) when not luchando is -- Cuando se esta luchando no se puede salir
      begin
         if Bola then -- Si hay bola la cojemos
            bolas := 1;
            bola := false;
         else
            bolas := 0;
         end if;

         bolas := bolas + bolasEsbirros; -- Devolvemos las bolas con las que sale de la Ciudad
         hayEsbirros := false; -- Indica que ya se ha ido de la ciudad
      end SalirEsbirros;
   end Ciudad;

   protected body BancoBolasGokuCia is
      procedure MeterBolas(id : integer; nbolas : integer) is
      begin
         -- Metemos las bolas en el Banco, en su cuenta
         Bolas(id) := nbolas;
      end MeterBolas;
      
      procedure DarBolas(id : integer; nbolas : out integer) is
      begin
         -- Sacamos las bolas de su cuenta.
         nbolas := Bolas(id);
         Bolas(id) := 0; -- Vaciamos su cuenta
      end DarBolas;

      procedure DarTodasBolas(nbolas : out integer) is
      begin
         -- Es para consultar en numero de bolas que hay en todal
         nbolas := 0;
         for i in 1..MAXGOKUCIA loop
            nbolas := nbolas + Bolas(i);
         end loop;
      end DarTodasBolas;
   end BancoBolasGokuCia;

   protected body BancoBolasEsbirros is
      procedure MeterBolas(id : integer; nbolas : integer) is
      begin
         -- Metemos las bolas en el Banco, en su cuenta
         Bolas(id) := nbolas;
      end MeterBolas;
      
      procedure DarBolas(id : integer; nbolas : out integer) is
      begin
         -- Sacamos las bolas de su cuenta.
         nbolas := Bolas(id);
         Bolas(id) := 0; -- Vaciamos su cuenta
      end DarBolas;

      procedure DarTodasBolas(nbolas : out integer) is
      begin
         -- Es para consultar en numero de bolas que hay en todal
         nbolas := 0;
         for i in 1..MAXESBIRROS loop
            nbolas := nbolas + Bolas(i);
         end loop;
      end DarTodasBolas;
   end BancoBolasEsbirros;

   task body GokuCIA is
      id: integer;
      bolas : integer := 0;
      nuestrasBolas : integer;
      enemigosBolas : integer;
      rand : float;
      salir : boolean := false;
      ciudad : integer := 1;
      algunAmigo : boolean;
   begin
      -- Inicializamos los valores
      id := idGokuCIA;
      Reset(G);
      loop
         ciudad := (ciudad mod MAXCIUDADES) + 1;
         Put("--> Goku o CIA con id:");Put(id,0);Put(" entra a la Ciudad ");Put(ciudad,0);New_Line;
         bancoGokuCIA.DarBolas(id, bolas); -- Cogemos las bolas para entrar en la Ciudad
         ciudades(ciudad).EntrarGokuCIA(id, bolas, algunAmigo);
         if algunAmigo then -- Salimos de la ciudad porque ya hay otro e los nuestros
            Put("<-- Goku o CIA con id:");Put(id,0);Put(" se va de la Ciudad");Put(ciudad,0);Put(" porque habia otro de su clan.");New_Line;
         else
            -- Generamos aleatoriamente el tiempo de busqueda en la ciudad
            rand := Random(G);
            delay Duration (rand * 10.0); -- Tiempo de busqueda
            ciudades(ciudad).SalirGokuCIA(bolas); -- Salimos de la ciudad con un numero de bolas
            Put("<$$ Goku o CIA con id:");Put(id,0);Put(" se va de la Ciudad");Put(ciudad,0);Put(" con ");Put(bolas,0);Put(" bolas.");New_Line;
         end if;
         bancoGokuCIA.MeterBolas(id, bolas); -- Metemos las bolas en el banco

         -- Vemos haber si alguen ha encontrado todas las bolas
         bancoEsbirros.DarTodasBolas(enemigosBolas);
         bancoGokuCIA.DarTodasBolas(nuestrasBolas);
         if nuestrasBolas = MAXBOLAS or enemigosBolas = MAXBOLAS then
            salir := true;
         end if;
         exit when salir; -- Salimos cuando ya no haya mas bolas

         rand := Random(G);
         delay Duration (rand * 20.0); -- Tiempo de cambiar de ciudad
      end loop;
      if nuestrasBolas = MAXBOLAS then
         Put("<+++ Goku o CIA con id:");Put(id,0);Put(" ha finalizado su busqueda porque han encontrado las todas las bolas.");New_Line;
      else
         Put("</// Goku o CIA con id:");Put(id,0);Put(" ha finalizado su busqueda porque los esbirros han encontrado las bolas.");New_Line;
      end if;
   end GokuCIA;

   task body Esbirro is
      id: integer;
      bolas : integer := 0;
      nuestrasBolas : integer;
      enemigosBolas : integer;
      rand : float;
      salir : boolean := false;
      ciudad : integer := 0;
      algunAmigo : boolean;
   begin
      -- Inicializamos los valores
      id := idEsbirro;
      Reset(G);
      loop
         ciudad := (ciudad mod MAXCIUDADES) + 1;
         Put("--> Esbirro con id:");Put(id,0);Put(" entra a la Ciudad ");Put(ciudad,0);New_Line;
         bancoEsbirros.DarBolas(id, bolas); -- Cogemos las bolas para entrar en la Ciudad
         ciudades(ciudad).EntrarEsbirros(id, bolas, algunAmigo);
         if algunAmigo then -- Salimos de la ciudad porque ya hay otro e los nuestros
            Put("<-- Esbirro con id:");Put(id,0);Put(" se va de la Ciudad");Put(ciudad,0);Put(" porque habia otro de su clan.");New_Line;
         else
            -- Generamos aleatoriamente el tiempo de busqueda en la ciudad
            rand := Random(G);
            delay Duration (rand * 10.0); -- Tiempo de busqueda
            ciudades(ciudad).SalirEsbirros(bolas); -- Salimos de la ciudad con un numero de bolas
            Put("<$$ Esbirro con id:");Put(id,0);Put(" se va de la Ciudad");Put(ciudad,0);Put(" con ");Put(bolas,0);Put(" bolas.");New_Line;
         end if;
         bancoEsbirros.MeterBolas(id, bolas); -- Metemos las bolas en el banco
         
         -- Vemos haber si alguen ha encontrado todas las bolas
         bancoGokuCia.DarTodasBolas(enemigosBolas);
         bancoEsbirros.DarTodasBolas(nuestrasBolas);
         if nuestrasBolas = MAXBOLAS or enemigosBolas = MAXBOLAS then
            salir := true;
         end if;
         exit when salir; -- Salimos cuando ya no haya mas bolas

         rand := Random(G);
         delay Duration (rand * 20.0); -- Tiempo de cambiar de ciudad
      end loop;
      if nuestrasBolas = MAXBOLAS then
         Put("<+++ Esbirro con id:");Put(id,0);Put(" ha finalizado su busqueda porque han encontrado las todas las bolas.");New_Line;
      else
         Put("</// Esbirro con id:");Put(id,0);Put(" ha finalizado su busqueda porque Goku y sus amigos han encontrado las bolas.");New_Line;
      end if;
   end Esbirro;

   -- Definimos unas variables para el reparto de las bolas
   reparto : integer := MAXBOLAS;
   aleatorio : float;
   cd : integer := 0;
   tieneBola : array (1..MAXCIUDADES) of boolean := (others => false);
begin
   Put("---- COMIENZAN LA AVENTURA");New_Line;New_Line;
   loop
      -- Repartimos las bolas
      cd := (cd mod MAXCIUDADES) + 1;
      Reset(G);
      aleatorio := Random(G);
      if not tieneBola(cd) then
         if aleatorio < 0.8 then -- Le damos una bola a esa ciudad.
            tieneBola(cd) := true;
            ciudades(cd).Iniciar(cd, true);
            reparto := reparto - 1;
         else
            ciudades(cd).Iniciar(cd, false);
         end if;
      end if;

      exit when reparto = 0;
   end loop;

   -- Arrancan Goku y sus amigos
   for i in 1..MAXGOKUCIA loop
      amigosGoku(i) := new GokuCIA(i);
   end loop;

   -- Arrancan los esbirros de Freeza
   for i in 1..MAXESBIRROS loop
      esbirros(i) := new Esbirro(i);
   end loop;
end DragonBallTProtAda;
